# 入门

# 简介

# 路由简介

  • 路由就是通过互联的网络把信息从源地址传送到目的地的活动,网络工程术语
  • 路由提供了两种机制:路由传送
    • 路由是决定数据包从来源到目的地的路径
    • 转送就是将数据转移
  • 路由表
    • 路由表本质就是一个映射表,决定了数据包的指向

# 后端 & 前端路由 🔥

  1. 后端渲染,服务端渲染(后端路由)阶段:前端请求一个 URL 后,由例如 Java 如 SpringMVC 中 @RequestMapping 来映射到 Java 代码中,并通过 Servlet 和 JSP 技术编写 HTML 页面(JS+CSS)并填充数据,最后返回给前端完整的 HTML,前端直接渲染展示即可。这种由后端处理 URL 和页面映射关系的称为后端路由

  2. 前后端分离阶段(Gmail 的Ajax 大量使用) :前端请求一个 URL 后,可能先从静态资源服务器获取一套 HTML+CSS+JS(服务器中有多套),之后再由 JS 发送 Ajax 请求到后端,由后端返回如 JSON 数据交给前端,前端使用 JS 进行数据填充,渲染 DOM。每次 URL 改变都会请求后端服务器拿静态资源和数据

  3. 单页面富应用(SPA页面),前后端分离加上前端路由阶段: 前端第一次请求 URL 后,会从如静态资源服务器获取一套完整的 HTML+CSS+JS(只有一个 HTML,其他 JS 或 CSS 可能会懒加载),之后的 URL 改变不会向后端服务器发送请求,页面不进行整体刷新,而是由其不同的 URL 页面自己发送 Ajax 请求获取数据并由JS 进行数据填充,渲染 DOM。这种由前端处理 URL 和页面的映射关系称为前端路由

    那么如何在 URL 改变后页面还不进行整体刷新呢?且看下面介绍。

# URL的hash & HTML5的history 🔥

TIP

Vue CLI 初始化项目时推荐选择 history 模式。URL看起来更友好,没有#这玩意

URL的 hash 是改变锚点#,其本质上改变的是 window.location.hash 属性,不是 href 属性

location.hash = '/user/10000'
http://localhost:8082/#/user/10000

location.hash = '/role/1'
http://localhost:8082/#/role/1

HTML5的 history。是一个栈结构

history.pushState('','','/user/100001')
http://localhost:8082/user/100001

history.pushState('','','/role/1')
http://localhost:8082/role/1

history.back()
http://localhost:8082/user/100001

history.forward()
http://localhost:8082/role/1

history.replaceState('','','/permission/100')
http://localhost:8082/permission/100
# 此时将上面的 http://localhost:8082/role/1 替换掉了,之后无法 back 回去,只能 back 到 http://localhost:8082/user/100001

# go 可以更灵活控制,相比 back,forward
history.go(-1)
history.go(1)
history.go(-2)
history.go(2)

Vue Router 就是将组件和 URL 映射

# 安装和配置 🔥

安装。有的推荐加上--save,代表安装到正式依赖中

npm install vue-router

模块化工程中使用必须要通过 Vue.use() 明确地安装路由插件功能,创建路由实例,传入路由映射配置

import Vue from "vue";
import VueRouter from "vue-router";
// 可以省略后缀(resolve.extensions配置的作用);不推荐在这里导入,推荐如下懒加载方式
import Home from "../components/Home";

// 安装路由插件功能
Vue.use(VueRouter);

// 路由映射配置
const routes = [
  {
    path: "/", // 或 "" 都可以
    name: "Index",
    // 重定向
    redirect: '/home'
  },
  {
    path: "/home",
    name: "Home",
    component: Home
  },
  {
    path: "/about",
    name: "About",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "@/components/About")
  },
  {
    path: '/user/:id',
    name: 'User',
    component: () => import('@/components/User')
  }
];

// 创建路由实例,传入路由映射配置
const router = new VueRouter({
  // history模式
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

// 导出
export default router;

在 Vue 实例中挂载创建的路由实例

// /src/main.js

import Vue from "vue";
import App from "./App.vue";
// 导入,./router 是一个目录,会自动导入 index 文件
import router from "./router";

Vue.config.productionTip = false;

// 挂载
new Vue({
  router,
  render: h => h(App)
}).$mount("#app");

# 标签路由 🔥

# 使用

<!-- /src/App.vue-->

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/home">Home</router-link>
      <router-link to="/about">About</router-link>
    </div>
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-active {
  color: #42b983;
}
</style>

那两个组件就不写了。

全局组件,最终被渲染成 a 标签,但是它只是标记路由指向类似一个 a 标签或者按钮一样,若是我们点击 a 标签要跳转页面或者要显示页面,所以就要用上它。

  • to属性:用于跳转到指定路径

  • tag属性:可以指定<router-link>之后渲染成什么组件使用<router-link to='/home' tag='button'>会被渲染成一个按钮,而不是 a 标签

  • relapce属性:在 history 模式下指定<router-link to='/home' tag='button' replace>使用replaceState 而不是 pushState,此时浏览器的返回按钮将不能返回替换前的路由

  • active-class属性:当<router-link>对应的路由匹配成功的时候,会自动给当前元素设置一个router-link-active的 class,设置active-class属性值可以修改默认的名称。

    • 在进行高亮显示的导航菜单或者底部 tabbar 时,会用到该属性。但是通常不会修改类的属性,会直接使用默认的router-link-active

    • <router-link to='/home' tag='button' active-class='active'>此时被选中的<router-link>就会有 active 的 class。

    • 如果每个<router-link>都要加上active-class='active',那就在路由里面统一更改。

      const router = new Router({
        //配置路由和组件之间的应用关系
        routes,
        mode: 'history',//修改模式为history
        linkActiveClass: 'active'
      })
      

# <router-view>

用来占位的,就是路由对应的组件展示的地方,该标签会根据当前的路径,动态渲染出不同的组件。

路由切换的时候切换的是它挂载的组件,其他不会发生改变。它默认使用 hash 模式,可以在 src/router/index.js 中配置修改为 history模式。

# 代码路由 🔥

<template>
  <div id="app">
    <div id="nav">
      <!-- 当然也可以使用 router-link 和 tag 来实现。但是必须有 to 属性,所以自己斟酌使用场景。
			这里 button 也不会添加 class -->
      <button @click="goHome">Home</button>
      <button @click="goAbout">About</button>
    </div>
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App',
  methods: {
    goHome() {
      // vue-router 给所有组件都添加了 $router 属性。还有replace方法!
      console.log(this.$route)
      // 不判断,则第二次点击同一路由报错
      if (this.$route.path !== '/home') {
        this.$router.push('/home')
      }
    },
    goAbout() {
      if (this.$route.path !== '/about') {
        this.$router.push('/about')
      }
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}
</style>

# 动态路由 🔥

由于路由中的 path是全匹配,例如/user查询所有用户,若此时 URL 为/user/1则匹配不到,但是这个1又不能写死!

{
    path: '/user/:id',
    name: 'User',
    component: () => import('@/components/User')
}
<router-link :to="'/user/'+user.id">User</router-link>
<!-- user.id 为动态获取的 -->
<template>
  <div>
    <h1>User</h1>
    <h2>{{userId}}</h2>
  </div>
</template>

<script>
export default {
  computed: {
    userId() {
      // 拿到路由上的动态参数
      return this.$route.params.id
    }
  }
}
</script>

<style>
</style>

注意,手动输入URL即使匹配到路由,也不会添加 active class!

# 路由懒加载 🔥

vue-router打包文件解析问题:打包时候 JS 太大,页面响应缓慢

如果组件模块化了,当路由被访问的时候才开始加载被选中的组件,这样就是路由懒加载。一个路由对应一个 JS 文件

component: () => import('@/components/User')

使用npm run build命令将之前创建的项目打包,打开 dist 文件夹,器目录结构如下:

image-20200623231001241

以下是 Vue CLI 2 的代码

  • app.xxx.js 是我们自己编写的业务代码
  • vendor.xxx.js 是第三方框架,例如 vue/vue-router/axios 等
  • mainfest.xxx.js 是为了打包的代码做底层支持的,一般是 webpack 帮我们做一些事情
  • 除了这三个还多了2个js,这2个js文件(0.5bxxx.js和1.e5xxx.js)分别是About和User组件,因为这2个组件是懒加载的所以被分开打包了。

此时因为是懒加载,需要用到这个组件的时候才会加载,所以不会一次性请求所有 JS。

# 嵌套路由 🔥

平常在一个home页面中,我们可能需要/home/news/home/message访问一些内容,一个路由映射一个组件就像后端一个api对应一个controller的一个requestMapping一样,访问两个路由也会分别渲染这两个组件。

要实现嵌套路由:

  • 创建对应的子组件,并且在路由映射router/index.js中配置对应的子路由。

  • 在组件内部使用<router-view>标签来占位。

// /router/index.js

import Vue from "vue";
import VueRouter from "vue-router";

// 安装路由插件功能
Vue.use(VueRouter);

// 路由映射配置
const routes = [
  {
    path: "", // 或 "/" 都可以
    name: "Index",
    // 重定向
    redirect: '/home'
  },
  {
    path: "/home",
    name: "Home",
    component: () => import("@/components/Home"),
    children: [
      {

        path: '', // 或 "/" 都可以
        // 后续也不这样使用,使用 keep-alive 来选择上次选择的路由
        redirect: 'news'
      },
      {
        // 不可以加 /,也不可以加 /home
        path: 'news',
        component: () => import("@/components/HomeNews")
      },
      {
        // 不可以加 /,也不可以加 /home
        path: 'message',
        component: () => import("@/components/HomeMessage")
      }
    ]
  },
  {
    path: "/about",
    name: "About",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "@/components/About")
  },
  {
    path: '/user/:id',
    name: 'User',
    component: () => import('@/components/User')
  }
];

// 创建路由实例,传入路由映射配置
const router = new VueRouter({
  // history模式
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

// 导出
export default router;
<!-- /components/Home.vue -->

<template>
  <div>
    <h1>Home</h1>
    <router-link to="/home/news">News</router-link><router-link to="/home/message">Message</router-link>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'Home'
}
</script>

# 传递参数 🔥

路由跳转间传递参数

# 方式一:动态路由 & params

  • 配置路由的格式:/user/:id
  • 传递时需要拼接,最终形成的路径:/user/123/user/xxx
  • 通过$route.params.id获取指定 id

代码查看动态路由

# 方式二:query

  • 配置路由的格式:/profile,也就是普通的配置

  • 传递的方式:对象中使用 query 的 key 作为传递的方式

  • 传递形成的路径:/profile?name=conanan&age=18这个query传递的是两个键值对

    /profile?user=%5Bobject%20Object%5D这个query传递的是一个对象的键值对,key为user,value是一个对象

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/home">Home</router-link>|
      <router-link to="/about">About</router-link>|
      <router-link :to="'/user/'+user.id">User</router-link>|
      <router-link :to="{path: '/profile', query: {name: 'conanan', age: 18}}">Profile</router-link>

      <!-- 当然也可以使用 router-link 和 tag 来实现。但是必须有 to 属性,所以自己斟酌使用场景。这里 button 也不会添加 class -->
      <!-- <button @click="goHome">Home</button>
      <button @click="goAbout">About</button>
      <button @click="goProfile">Profile</button>-->
    </div>
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      user: {
        id: 10001
      }
    }
  },
  methods: {
    goHome() {
      // vue-router 给所有组件都添加了 $router 属性。还有replace方法!
      console.log(this.$route)
      // 不判断,则第二次点击同一路由报错
      if (this.$route.path !== '/home') {
        this.$router.push('/home')
      }
    },
    goAbout() {
      if (this.$route.path !== '/about') {
        this.$router.push('/about')
      }
    },
    goProfile() {
      if (this.$route.path !== '/profile') {
        this.$router.push({
          path: '/profile',
          // query: { name: 'conanan', age: 18 }
          query: {
            user: {
              name: 'conanan',
              age: 25
            }
          }
        })
      }
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-active {
  color: #42b983;
}
</style>

# $router & $route 区别 🔥

vue 全局对象this.$router与 main.js 导入的 router 对象是一个对象,也就是我们/router/index.js导出的对象router,都是同一个 VueRouter 对象。因为所有的组件都继承自 Vue 类的原型(prototype)

this.$route对象是当前处于活跃的路由,就是 router 中配置的映射关系,有 params 和 query 属性可以用来传递参数。